//--------------------------------------------------------------------------------------
// File: normalmap.cpp
//--------------------------------------------------------------------------------------
#include "DXUT.h"
#include "DXUTcamera.h"
#include <vector>
#include <string>

struct Mtrl
{
	D3DXCOLOR ambient;
	D3DXCOLOR diffuse;
	D3DXCOLOR spec;
	float specPower;
};

struct DirLight
{
	D3DXCOLOR ambient;
	D3DXCOLOR diffuse;
	D3DXCOLOR spec;
	D3DXVECTOR3 dir;
};

D3DVERTEXELEMENT9 NMapVertexElements[] = 
{
	{0, 0,  D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
	{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TANGENT, 0},
	{0, 24, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BINORMAL, 0},
	{0, 36, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0},
	{0, 48, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
	D3DDECL_END()
};	

CFirstPersonCamera g_camera;
ID3DXMesh *g_sceneMesh = NULL;
DWORD g_numMtrl;
std::vector<IDirect3DTexture9*> g_textures;
std::vector<IDirect3DTexture9*> g_normalTextures;
std::vector<Mtrl> g_mtrls;
DirLight g_dirLight;

ID3DXEffect *g_effect;
D3DXHANDLE g_hNormalMapTech;
D3DXHANDLE g_hWorldInvs;
D3DXHANDLE g_hWVP;
D3DXHANDLE g_hMtrl;
D3DXHANDLE g_hLight;
D3DXHANDLE g_hEyePos;
D3DXHANDLE g_hColorTexture;
D3DXHANDLE g_hNormalTexture;

void BuildFX(IDirect3DDevice9* pd3dDevice);

//--------------------------------------------------------------------------------------
// Rejects any D3D9 devices that aren't acceptable to the app by returning false
//--------------------------------------------------------------------------------------
bool CALLBACK IsD3D9DeviceAcceptable( D3DCAPS9* pCaps, D3DFORMAT AdapterFormat, D3DFORMAT BackBufferFormat,
	bool bWindowed, void* pUserContext )
{
	// Typically want to skip back buffer formats that don't support alpha blending
	IDirect3D9* pD3D = DXUTGetD3D9Object();
	if( FAILED( pD3D->CheckDeviceFormat( pCaps->AdapterOrdinal, pCaps->DeviceType,
		AdapterFormat, D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING,
		D3DRTYPE_TEXTURE, BackBufferFormat ) ) )
		return false;

	return true;
}


//--------------------------------------------------------------------------------------
// Before a device is created, modify the device settings as needed
//--------------------------------------------------------------------------------------
bool CALLBACK ModifyDeviceSettings( DXUTDeviceSettings* pDeviceSettings, void* pUserContext )
{
	return true;
}


//--------------------------------------------------------------------------------------
// Create any D3D9 resources that will live through a device reset (D3DPOOL_MANAGED)
// and aren't tied to the back buffer size
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnD3D9CreateDevice( IDirect3DDevice9* pd3dDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc,
	void* pUserContext )
{
	ID3DXMesh *tempMesh;
	ID3DXBuffer *d3dMtrlBuffer;
	D3DXLoadMeshFromX(L"BasicColumnScene.x", D3DXMESH_MANAGED, pd3dDevice, NULL, &d3dMtrlBuffer, NULL, &g_numMtrl, &tempMesh);
	D3DXMATERIAL *d3dMtrl = (D3DXMATERIAL*)d3dMtrlBuffer->GetBufferPointer();
	for(DWORD i = 0; i < g_numMtrl; i++)
	{
		if(d3dMtrl[i].pTextureFilename)
		{
			Mtrl mtrl;
			mtrl.ambient = d3dMtrl[i].MatD3D.Ambient;
			mtrl.diffuse = d3dMtrl[i].MatD3D.Diffuse;
			mtrl.spec = d3dMtrl[i].MatD3D.Specular;
			mtrl.specPower = d3dMtrl[i].MatD3D.Power;
			g_mtrls.push_back(mtrl);

			IDirect3DTexture9* texture;
			D3DXCreateTextureFromFileA(pd3dDevice, d3dMtrl[i].pTextureFilename, &texture);
			g_textures.push_back(texture);

			std::string nmapName(d3dMtrl[i].pTextureFilename);
			nmapName.replace(nmapName.find("color"), 5, "nmap");
			D3DXCreateTextureFromFileA(pd3dDevice, nmapName.c_str(), &texture);
			g_normalTextures.push_back(texture);
		}
	}

	D3DXCOLOR white(1.0f, 1.0f, 1.0f, 1.0f);
	g_dirLight.ambient = white;
	g_dirLight.diffuse = white;
	g_dirLight.spec = white;
	g_dirLight.dir = D3DXVECTOR3(1.0f, -1.0f, 0.5f);

	ID3DXMesh *clonedMesh;
	tempMesh->CloneMesh(D3DXMESH_MANAGED, NMapVertexElements, pd3dDevice, &clonedMesh);
	D3DXComputeTangentFrameEx(
		clonedMesh, // Input mesh
		D3DDECLUSAGE_TEXCOORD, 0, // Vertex element of input tex-coords.  
		D3DDECLUSAGE_BINORMAL, 0, // Vertex element to output binormal.
		D3DDECLUSAGE_TANGENT, 0,  // Vertex element to output tangent.
		D3DDECLUSAGE_NORMAL, 0,   // Vertex element to output normal.
		0, // Options
		0, // Adjacency
		0.01f, 0.25f, 0.01f, // Thresholds for handling errors
		&g_sceneMesh, // Output mesh
		0);
	tempMesh->Release();
	clonedMesh->Release();

	BuildFX(pd3dDevice);
	return S_OK;
}


//--------------------------------------------------------------------------------------
// Create any D3D9 resources that won't live through a device reset (D3DPOOL_DEFAULT) 
// or that are tied to the back buffer size 
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnD3D9ResetDevice( IDirect3DDevice9* pd3dDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc,
	void* pUserContext )
{
	g_camera.SetProjParams(D3DX_PI / 4, pBackBufferSurfaceDesc->Width / (float)pBackBufferSurfaceDesc->Height,
		1.0f, 1000.0f);
	return S_OK;
}


//--------------------------------------------------------------------------------------
// Handle updates to the scene.  This is called regardless of which D3D API is used
//--------------------------------------------------------------------------------------
void CALLBACK OnFrameMove( double fTime, float fElapsedTime, void* pUserContext )
{
	g_camera.FrameMove(fElapsedTime);
}


//--------------------------------------------------------------------------------------
// Render the scene using the D3D9 device
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D9FrameRender( IDirect3DDevice9* pd3dDevice, double fTime, float fElapsedTime, void* pUserContext )
{
	HRESULT hr;

	// Clear the render target and the zbuffer 
	V( pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_ARGB( 0, 45, 50, 170 ), 1.0f, 0 ) );

	// Render the scene
	if( SUCCEEDED( pd3dDevice->BeginScene() ) )
	{
		g_effect->SetTechnique(g_hNormalMapTech);
		D3DXMATRIX world, view, proj, worldInvs;
		D3DXMatrixTranslation(&world, 0.0f, 0.0f, 5.0f);
		view = *g_camera.GetViewMatrix();
		proj = *g_camera.GetProjMatrix();
		D3DXMatrixInverse(&worldInvs, NULL, &world);
		g_effect->SetMatrix(g_hWorldInvs, &worldInvs);
		g_effect->SetMatrix(g_hWVP, &(world * view * proj));
		g_effect->SetValue(g_hLight, &g_dirLight, sizeof(DirLight));
		g_effect->SetValue(g_hEyePos, g_camera.GetEyePt(), sizeof(D3DXVECTOR3));
		
		g_effect->Begin(0, 0);
		g_effect->BeginPass(0);
		for(DWORD i = 0; i < g_numMtrl; i++)
		{
			g_effect->SetValue(g_hMtrl, &g_mtrls[i], sizeof(Mtrl));
			g_effect->SetTexture(g_hColorTexture, g_textures[i]);
			g_effect->SetTexture(g_hNormalTexture, g_normalTextures[i]);
			g_effect->CommitChanges();

			g_sceneMesh->DrawSubset(i);
		}
		g_effect->EndPass();
		g_effect->End();
		V( pd3dDevice->EndScene() );
	}
}


//--------------------------------------------------------------------------------------
// Handle messages to the application 
//--------------------------------------------------------------------------------------
LRESULT CALLBACK MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
	bool* pbNoFurtherProcessing, void* pUserContext )
{
	g_camera.HandleMessages(hWnd, uMsg, wParam, lParam);
	return 0;
}


//--------------------------------------------------------------------------------------
// Release D3D9 resources created in the OnD3D9ResetDevice callback 
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D9LostDevice( void* pUserContext )
{
}


//--------------------------------------------------------------------------------------
// Release D3D9 resources created in the OnD3D9CreateDevice callback 
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D9DestroyDevice( void* pUserContext )
{
	for(auto iter = g_textures.begin(); iter != g_textures.end(); ++iter)
	{
		(*iter)->Release();
	}
	for(auto iter = g_normalTextures.begin(); iter != g_normalTextures.end(); ++iter)
	{
		(*iter)->Release();
	}
	g_sceneMesh->Release();
	g_effect->Release();

	g_textures.resize(0);
	g_normalTextures.resize(0);
	g_mtrls.resize(0);
}


//--------------------------------------------------------------------------------------
// Initialize everything and go into a render loop
//--------------------------------------------------------------------------------------
INT WINAPI wWinMain( HINSTANCE, HINSTANCE, LPWSTR, int )
{
	// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
	_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif

	// Set the callback functions
	DXUTSetCallbackD3D9DeviceAcceptable( IsD3D9DeviceAcceptable );
	DXUTSetCallbackD3D9DeviceCreated( OnD3D9CreateDevice );
	DXUTSetCallbackD3D9DeviceReset( OnD3D9ResetDevice );
	DXUTSetCallbackD3D9FrameRender( OnD3D9FrameRender );
	DXUTSetCallbackD3D9DeviceLost( OnD3D9LostDevice );
	DXUTSetCallbackD3D9DeviceDestroyed( OnD3D9DestroyDevice );
	DXUTSetCallbackDeviceChanging( ModifyDeviceSettings );
	DXUTSetCallbackMsgProc( MsgProc );
	DXUTSetCallbackFrameMove( OnFrameMove );

	// TODO: Perform any application-level initialization here

	// Initialize DXUT and create the desired Win32 window and Direct3D device for the application
	DXUTInit( true, true ); // Parse the command line and show msgboxes
	DXUTSetHotkeyHandling( true, true, true );  // handle the default hotkeys
	DXUTSetCursorSettings( true, true ); // Show the cursor and clip it when in full screen
	DXUTCreateWindow( L"EmptyProject" );
	DXUTCreateDevice( true, 640, 480 );

	// Start the render loop
	DXUTMainLoop();

	// TODO: Perform any application-level cleanup here

	return DXUTGetExitCode();
}

void BuildFX(IDirect3DDevice9* pd3dDevice)
{
	// Create the FX from a .fx file.
	ID3DXBuffer* errors = 0;
	D3DXCreateEffectFromFile(pd3dDevice, L"NormalMap.fx", 
		0, 0, D3DXSHADER_DEBUG, 0, &g_effect, &errors);
	if( errors )
		MessageBoxA(0, (char*)errors->GetBufferPointer(), 0, 0);

	g_hNormalMapTech = g_effect->GetTechniqueByName("NormalMapTech");
	g_hWorldInvs = g_effect->GetParameterByName(0, "gWorldInvs");
	g_hWVP = g_effect->GetParameterByName(0, "gWVP");
	g_hMtrl = g_effect->GetParameterByName(0, "gMtrl");
	g_hLight = g_effect->GetParameterByName(0, "gLight");
	g_hEyePos = g_effect->GetParameterByName(0, "gEyePos");
	g_hColorTexture = g_effect->GetParameterByName(0, "gColorTexture");
	g_hNormalTexture = g_effect->GetParameterByName(0, "gNormalTexture");
}